﻿using ServerListerner.Helper;

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace ServerListerner.Connections
{
    internal class TCPConnection : ITCPConnection
    {
        private Socket _socket;
        private byte[] _buffer;
        private int _bufferLen;
        private int _sendBufferLen;
        private List<byte> _received = new List<byte>();
        private bool IsClosed = false;
        private MpscChannel<bool> _channel;
        public Action<ITCPConnection>? OnConnection { get; set; }
        public Action<ITCPConnection, byte[]>? OnReceived { get; set; }
        public Action<ITCPConnection>? OnClosed { get; set; }
        public Action<ITCPConnection, string, Exception>? OnError { get; set; }
        public Guid? ConnectionId { get; private set; }
        public EndPoint? RemoteEndPoint { get; private set; }

        public TCPConnection(Socket socket,MpscChannel<bool> channel, Action<ITCPConnection>? onConnection)
        {
            this._socket = socket;
            this._channel = channel;
            RemoteEndPoint = this._socket.RemoteEndPoint;
            this.ConnectionId = Guid.NewGuid();
            this._bufferLen = this._socket.ReceiveBufferSize;
            this._sendBufferLen= this._socket.SendBufferSize;
            this._buffer = new byte[this._bufferLen];
            onConnection?.Invoke(this);
            OnConnection?.Invoke(this);
            _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
            _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
            _socket.ReceiveBufferSize = 128 * 1024;
            _ = Task.Run(ReceivedChannel);
            _ = Task.Run(StartReceived);
        }
        private void ReceivedChannel()
        {
            try
            {
                while (_channel != null)
                {
                    if (_channel.Receive())
                    {
                        this.Dispose();
                        return;
                    }
                }
                this.Dispose();
            }
            catch
            {}  
        }
        private void StartReceived()
        {
            try
            {
                while (!IsClosed)
                {
                    this._buffer = null;
                    this._buffer = new byte[this._bufferLen];
                    if (!this._socket.Connected) this.Close();
                    if (IsClosed) return;
                    var iar = this._socket.BeginReceive(this._buffer, 0, _bufferLen, SocketFlags.None, null, null);
                    int _readLen = this._socket.EndReceive(iar);
                    if (_readLen == 0) Close();
                    if (IsClosed) return;
                    if (this._received == null)
                        this._received = new List<byte>();
                    this._received.AddRange(this._buffer.Take(_readLen));
                    Task.Delay(1).Wait();
                    if (this._socket.Available <= 0)
                    {
                        OnReceived?.Invoke(this, this._received.ToArray());
                        this._received?.Clear();
                    }
                }
            }
            catch (Exception ex)
            {
                Close();
                //OnError?.Invoke(this, "Received Error", ex);
            }
        }
        public void Send(byte[] bytes)
        {
            try
            {
                while (bytes.Length > 0)
                {
                    if (!_socket.Connected) Close();
                    if (IsClosed) return;
                    var send = bytes.Take(this._sendBufferLen).ToArray();
                    int len = send.Length > this._sendBufferLen ? this._sendBufferLen : send.Length;
                    bytes = bytes.Skip(len).ToArray();
                    var iar = _socket.BeginSend(send, 0, len, SocketFlags.None, null, null);
                    this._socket.EndSend(iar);
                }
            }
            catch (Exception ex)
            {
                Close();
                //OnError?.Invoke(this, "Send Error", ex);
            }
        }
        public void Send(string _str, Encoding encoding = null)
        {
            encoding = encoding ?? Encoding.UTF8;
            this.Send(encoding.GetBytes(_str));
        }
        protected void CloseReceived() => this.Close();

        private void Close()
        {
            try
            {
                this.IsClosed = true;
                this.OnClosed?.Invoke(this);
                this.CloseSocket();
                this.SetNull();
            }
            catch (Exception ex)
            {
            }
        }
        private void CloseSocket()
        {
            this._socket.Shutdown(SocketShutdown.Both);
            this._socket.Close();
            this._socket.Dispose();
            GC.Collect();
        }
        private void SetNull()
        {
            OnConnection = null;
            OnReceived = null;
            OnClosed = null;
            OnError = null;
        }

        public void Dispose()
        {
            this.Close();
        }
    }
}
